package com.mkf.droidsat;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Set;
import	java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.lang.Math;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.widget.Toast;


// This class is intended to provide the ability to control de positioning of a "real" telescope.
// First implementation is done for NexStar Celestron telescopes, maybe sometime will be extended to LX200 (Meade)
// It currently tries to lock the telescope on the selected satellite when tracking is activated
//
// A TelescopeControl is constructed from a set of bluetooth paired devices
// The constructor searches for a proper telescope and on success starts a periodic task to
// read telescope's position and if tracking is enabled slew the telescope to the selected sat. coords 
//
public class TelescopeControl extends Thread {

	public static final int	TELESCOPE_DEBUG	= 0;
	private static final long TELESCOPE_REPLY_TIMEOUT = 3500;
	private static final long TELESCOPE_INIT_TIMEOUT = 500;
	// From developer.android.com BluetoothDevice documentation:
	// Hint: If you are connecting to a Bluetooth serial board then try using the
	// well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB. However if you are
	// connecting to an Android peer then please generate your own unique UUID.
	private static final UUID mmUUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
	public static BluetoothSocket mmSocket;
	private static InputStream mmInStream;
	private static OutputStream mmOutStream;
	public static int bufferLength=2048;
	public static byte[] buffer = new byte[bufferLength];
	public static int bufferPointer;
	public static Timer mmTimer;
	public static double aimAz;
	public static double aimEl;
	public static double aimAzRadians;
	public static double aimElRadians;
	public static double slewRateAz,slewRateEl;
	// max slew rate is assumed as 3deg/sec so 180*60 arcsec/sec.
	// raw value to send to telescope is 4* slew rate in arcsec/sec
	public static final double MAX_SLEW_RATE_AZ = (double)(180*60*4);
	public static final double MAX_SLEW_RATE_EL = (double)(180*60*4);
	public static double gainPropAz,gainIntAz,gainDerAz;
	public static double gainPropEl,gainIntEl,gainDerEl;
	public static int cycleTime;
	public static long timeStamp;
	private static int errCycleCount;
	private static final int MAX_ERR_CYCLES = (int)(4*3.5);
	// flag to keep telescope aim at screen center
	public static boolean lockOnTelescope;
	public static double offsetAimAzRadians,offsetAimElRadians;
	public static boolean invertElCorrection;
	public static boolean enableSearch;
	public static boolean pauseSearch = false;	// while screen is touched spiral is paused
	public static double searchAngle=0;			// Radians of spiral
	public static double searchAngle_disc=0;		// discretized version of searchAngle
	public static double searchAngle_disc_old=0;		// discretized version of searchAngle
	public static double	searchAngle_step;		// View-angle step desired for search
	public static double searchAngleStep;			// Angle of spiral for next step
	public static double searchSpeed;				// Radians per sec. of view-angle
	public static double searchSpiralSteep;		// Radians of view-angle per turn of spiral steepness
	public static double spiralOffsetAzRadians, spiralOffsetElRadians;

	
	// Periodic task for keeping telescope as precise as possible aimed to selected satellite
	// also update global variables to let onDraw() method show actual telescope aim coordinates
	private static class TelescopeUpdate extends TimerTask {
		int i;
		long l;
		private long Az,El;
		private long AzPrev;
		private long ElPrev;
		private double errAz,errEl;
		private double errAzPrev,errElPrev;		// Previous error for derivative compensation
		private double compAzInt,compElInt;		// Accumulated integral compensation
		private double timeLapse;
		private byte[] localBuffer;
		private int localPointer;
		private double d;
		
		@Override
		public void run(){
			// Recover telescope's reported position from buffer
			// check if we have valid data on buffer
			localBuffer = buffer;
			localPointer = bufferPointer;
			if((localPointer == 10) && (localBuffer[9] == '#') && (localBuffer[4] == ',')){
				// valid reply detected, reset error counter
				errCycleCount = 0;
				// get Az value from 4 hex nibbles
				Az = 4096 * (long)Character.digit((char)localBuffer[0], 16);
				Az += 256 * (long)Character.digit((char)localBuffer[1], 16);
				Az += 16 * (long)Character.digit((char)localBuffer[2], 16);
				Az += (long)Character.digit((char)localBuffer[3], 16);
				// get El value from 4 hex nibbles
				El = 4096 * (long)Character.digit((char)localBuffer[5], 16);
				El += 256 * (long)Character.digit((char)localBuffer[6], 16);
				El += 16 * (long)Character.digit((char)localBuffer[7], 16);
				El += (long)Character.digit((char)localBuffer[8], 16);
				
				aimAz = (double)Az / 65536 * 360;
				aimEl = (double)El / 65536 * 360;
				if(aimEl > 180){
					aimEl = aimEl - 360;
				}
				
				aimAzRadians = (double) (aimAz / (360/(2*Math.PI)));
				aimElRadians = (double) (aimEl / (360/(2*Math.PI)));

				// if tracking enabled we will try to keep telescope aimed at the selected satellite...
				if(ShowSatellites.trackEnable){
					if (lockOnTelescope){
						// move screen to show telescope aim at center
						ShowSatellites.stereoView.setHeading ((float)aimAz);
						ShowSatellites.stereoView.setPitch((float)aimEl);
					}

					// get time lapse since last error calculation 
					timeLapse = 1e-9 * ((l=System.nanoTime()) - timeStamp);	// scale to seconds
					// store current time for next run
					timeStamp = l;

					// if search is enabled implement a spiral-shaped wandering motion around calculated coordinates
					if(enableSearch){
						if(!pauseSearch){
							// TODO: make search angle time-discrete
							
							if(searchAngle == 0){
								// spiral is just beginning, initialize anglestep
								searchAngleStep = searchAngle_step / (4 * searchSpiralSteep);
							}
							// Tangential speed of the spiral-motion should be constant, we try a simplified approach
							// to normalize it.
							searchAngle += searchSpeed / ((searchAngle + 2) * searchSpiralSteep) * timeLapse;
							
							if(searchAngle_step == 0){
								// step size of 0 means user does not want discretized spiral
								searchAngle_disc = searchAngle;
							}
							else{
								// calculate discrete angle of spiral
								if(searchAngle > searchAngleStep){
									searchAngle_disc = searchAngle;
									// calculate angle step of spiral, step is bigger near center of spiral and needs to
									// be smaller as spiral spreads out. This is to try keep constant steps on
									// celestial coordinates... 
									searchAngleStep += searchAngle_step / ((searchAngle_disc + 3) * searchSpiralSteep);
								}
							}
						}
						else{
							//l=l;
						}
						spiralOffsetAzRadians = searchSpiralSteep * (searchAngle_disc) * Math.cos(searchAngle_disc);
						spiralOffsetElRadians = searchSpiralSteep * (searchAngle_disc) * Math.sin(searchAngle_disc);
						// the resulting spiral for these equations would be fine near horizon (small Elev)
						// as elev. increases the spiral becomes elliptically shaped
						// try a simplistic approach to correct spiral shape assuming spiral is very small...
						d = 1/Math.cos(aimElRadians);
						if (d > 10) d=10;
						spiralOffsetAzRadians *= d;						
					}
					else{
						spiralOffsetAzRadians = 0;
						spiralOffsetElRadians = 0;
					}
					
					// Update slew speed for azimuth and elevation telescope's axis
					// we need a second-order correction scheme in order to keep down position error on an
					// objective that is constantly moving. So we will implement a PID controller:
					
					// first obtain position error
					// This error is calculated from a reply that the telescope gave several milliseconds ago
					// so aim coordinates are no longer these.
					// there sure are ways to handle this timing issue, for now all we do is assume there will be
					// some difference and hope it is static so we may compensate it by hand (by the human-observer)
					errAz = ShowSatellites.selectedSatPosn.azRadians - aimAzRadians + offsetAimAzRadians + spiralOffsetAzRadians;
					// check if more then half turn away...
					if(errAz > Math.PI){
						errAz -= 2*Math.PI;
					}
					else if(errAz < (-Math.PI)){
						errAz += 2*Math.PI;
					}
					errEl = ShowSatellites.selectedSatPosn.elRadians - aimElRadians + offsetAimElRadians + spiralOffsetElRadians;
					// check if more then half turn away...
					if(errEl > Math.PI){
						errEl -= 2*Math.PI;
					}
					else if(errEl < (-Math.PI)){
						errEl += 2*Math.PI;
					}
					
					// calculate an Azimuth slew rate to try to compensate the position error
					// calculation takes into account position error thru gainProp, variation of position error thru
					// gainDer and accumulation of position error thru gainInt
					// We could take it a bit forward and calculate a velocity error, maybe sometime... 
					slewRateAz = errAz * gainPropAz;
					// TODO this derivative component should be lowpass filtered somehow
					if(errAzPrev == 0){
						errAzPrev = errAz;
					}
					slewRateAz += ((errAz - errAzPrev) / timeLapse) * gainDerAz;
					if(slewRateAz > MAX_SLEW_RATE_AZ){
						slewRateAz = MAX_SLEW_RATE_AZ;
					}
					else if(slewRateAz < (- MAX_SLEW_RATE_AZ)){
						slewRateAz = - MAX_SLEW_RATE_AZ;
					}
					else{
						// accumulate and add integral compensation only if not saturated already!
						// TODO should add some algorithm to prevent windup effect
						compAzInt += errAz * timeLapse * gainIntAz;
						if((slewRateAz + compAzInt) > MAX_SLEW_RATE_AZ){
							compAzInt = MAX_SLEW_RATE_AZ - slewRateAz;
						}
						else if((slewRateAz + compAzInt) < (- MAX_SLEW_RATE_AZ)){
							compAzInt = MAX_SLEW_RATE_AZ + slewRateAz;
						}
						// add clamped integral compensation to slew rate
						slewRateAz += compAzInt; 
					}
	
					// calculate an Elevation slew rate
					slewRateEl = errEl * gainPropEl;
					// TODO this derivative component should be lowpass filtered somehow
					if(errElPrev == 0){
						errElPrev = errEl;
					}
					slewRateEl += ((errEl - errElPrev) / timeLapse) * gainDerEl;
					if(slewRateEl > MAX_SLEW_RATE_EL){
						slewRateEl = MAX_SLEW_RATE_EL;
					}
					else if(slewRateEl < (- MAX_SLEW_RATE_EL)){
						slewRateEl = - MAX_SLEW_RATE_EL;
					}
					else{
						// accumulate and add integral compensation only if not saturated already!
						// TODO should add some algorithm to prevent windup effect
						compElInt += errEl * timeLapse * gainIntEl;
						if((slewRateEl + compElInt) > MAX_SLEW_RATE_EL){
							compElInt = MAX_SLEW_RATE_EL - slewRateEl;
						}
						else if((slewRateEl + compElInt) < (- MAX_SLEW_RATE_EL)){
							compElInt = MAX_SLEW_RATE_EL + slewRateEl;
						}
						// add clamped integral compensation to slew rate
						slewRateEl += compElInt;
					}
					
					// prepare slew commands for telescope
					byte[] command = new byte[8];
					command[0] = (byte)"P".charAt(0);
					command[1] = 3;
					command[2] = 16;
					if(slewRateAz >= 0){
						command[3] = 6;
						command[4] = (byte)(slewRateAz / 256);
						command[5] = (byte)(slewRateAz - ((double)command[4] * 256));
					}
					else{
						command[3] = 7;
						command[4] = (byte)(-slewRateAz / 256);
						command[5] = (byte)((-slewRateAz) - ((double)command[4] * 256));
					}
					command[6] = 0;
					command[7] = 0;
					// send Az slew command
					write(command);
	
					// Elevation command only changes on some bytes:
					command[2] = 17;
					if(slewRateEl >= 0){
						command[3] = 6;
						command[4] = (byte)(slewRateEl / 256);
						command[5] = (byte)(slewRateEl - ((double)command[4] * 256));
					}
					else{
						command[3] = 7;
						command[4] = (byte)(-slewRateEl / 256);
						command[5] = (byte)((-slewRateEl) - ((double)command[4] * 256));
					}
					// send El slew command
					write(command);
	
					// store errors to use as previous for next execution of this routine
					errAzPrev = errAz;
					errElPrev = errEl;
				}
				// ask the telescope to report Azm-Alt in low resolution (aprox 20 arc-sec)
				// we will read response on next execution of this task
				TelescopeListen.clear = true;	// clear buffer so we have only this commands reply on buffer 
				TelescopeListen.interrupt = false;	// we don't want to be interrupted at reception of the end-character
				write("Z".getBytes());
			}
			else{
				// No valid reply from telescope
				if(++errCycleCount > MAX_ERR_CYCLES){
					if(TELESCOPE_DEBUG != 0){
						Toast msg = Toast
								.makeText(
										ShowSatellites.instance,
										"Too many consecutive Comm errors with telescope, cancelling track.",
										Toast.LENGTH_LONG);
						msg.show();
					}
					// close comms
					TelescopeControl.close();
					// tracking was enabled, user wants to shut it down
					ShowSatellites.trackEnable = false;
					return;
				}
				// ask the telescope to report Azm-Alt in low resolution (aprox 20 arc-sec)
				// we will read response on next execution of this task
				TelescopeListen.clear = true;	// clear buffer so we have only this commands reply on buffer 
				TelescopeListen.interrupt = false;	// we don't want to be interrupted at reception of the end-character
				write("Z".getBytes());
			}
		}
	}
	
	
	// constructor from a set of bluetooth devices
	public TelescopeControl(Set<BluetoothDevice> pairedDevices){
		// Construction of TelescopeControl from set of paired devices is complicated
		// we need to search all the devices looking for a device which we can open
		// and also establish a connection
		// and it has to respond to 'K' echo command of NexStar
		// All this is done by this method:
		mmSocket = searchBtDevices(pairedDevices);
		// if connection is well established setup a periodic task
		// this task would update telescope movement rate and telescope's aim point on screen
		if (mmSocket != null){
			// Cancel telescope's tracking (sideral, solar, moon, whatever...)
			TelescopeListen.clear = true;
			TelescopeListen.interrupt = true;
			byte[] command = new byte[2];
			command[0] = (byte)"T".charAt(0);
			command[1] = 0;
			write(command);
			try{
				sleep(TELESCOPE_REPLY_TIMEOUT);
			}catch(InterruptedException e){	}
			if((TELESCOPE_DEBUG != 0) && (buffer[0] != '#')){
				Toast msg = Toast
						.makeText(
								ShowSatellites.instance,
								"No response to sideral-tracking cancel",
								Toast.LENGTH_LONG);
				msg.show();
			}
			// to enable set center coordinates at telescope aim position we need to disable
			// sensor movements detection
			if(lockOnTelescope){
				ShowSatellites.sensorOrientationOn = false;
			}
			// start control thread
			timeStamp = System.nanoTime();
			errCycleCount = 0;
			offsetAimAzRadians = 0;
			offsetAimElRadians = 0;					
			if(mmTimer != null){
				mmTimer.cancel();
				mmTimer.purge();
				mmTimer = null;
			}
			mmTimer = new Timer();
			TimerTask t = new TelescopeUpdate();
			mmTimer.scheduleAtFixedRate(t, cycleTime, cycleTime);
		}
	}

	// this method initializes input and output sockets
	private static boolean initStreams(BluetoothSocket socket){
		mmSocket = socket;
		InputStream tmpIn = null;
		OutputStream tmpOut = null;
		
		try{
			tmpIn = socket.getInputStream();
			tmpOut = socket.getOutputStream();
		} catch (IOException e){
			return false;
		}
		mmInStream = tmpIn;
		mmOutStream = tmpOut;
		return true;
	}
	
	
	// runnable class to listen to telescope on independent thread
	// otherwise execution would block while waiting for answers from telescope
	private static class TelescopeListen implements Runnable {
		// this method starts the listening on the input stream and stores every byte received on buffer
		// also counts number of bytes received on bytes
		// to stop this thread change the listen field to false
		private static boolean listen;
		private static boolean clear;
		private static boolean interrupt;
			
		// this method is run when starting the listening thread
		//
		// listen property must be true all the time we want to be listening
		// if set to false this method will exit and the thread will die
		// watch-out for this method calls some methods that BLOCK executions!
		// in particular read() method of instream blocks while waiting for bytes to be captured
		//
		// SO not only listen property needs to be false to exit this thread, if a call is blocking
		// this will prevent this method to finish.
		//
		// if interrupt property is true this method tries to send and interrupt request to this
		// threads parent in order to signal an end-of-sleep event to make the communication process
		// responsive
		@Override
		public void run() {

			int i;
			listen = true;
			interrupt = false;
			
			while(listen){
				if(clear){
					Arrays.fill(buffer, (byte) -1);
					bufferPointer = 0;
					clear = false;
				}
				// read the input stream waiting for valid bytes received
				try{
					i = mmInStream.read();
					if(clear){
						Arrays.fill(buffer, (byte) -1);
						bufferPointer = 0;
						clear = false;
					}
					if (i >= 0){
						buffer[bufferPointer++] = (byte)i;
						if((i == '#') && interrupt){
							//readReply();
							// Send interrupt signal to parent thread (in case it was asleep)
							Thread.currentThread().getThreadGroup().getParent().interrupt();
						}
					}
				}catch(IOException e){}
			}
		}
	}

	public static void readReply(){
		
	}
	
	// tool to write bytes to socket
	public static void write(byte[] bytes){
		try{
			mmOutStream.write(bytes);
		}catch(IOException e){}
	}
	
	// tool to close communications
	// first try to end the listening thread
	// then close stram and socket
	public static void close(){
		endTracking();
		if(mmTimer != null){
			mmTimer.cancel();
			mmTimer.purge();
			mmTimer = null;
		}
		try{
			//if(mmInStream != null){
			//	mmInStream.close();
			//	mmInStream = null;
			//}
			TelescopeListen.listen = false;
			if(mmSocket != null){
				mmSocket.close();
				mmSocket = null;
			}
		}catch(IOException e){}
	}

	
	// Restart tracking assuming connection not lost
	public static boolean restartTracking(){
		// check if stream still valid and if periodic control task still running
		// this task would update telescope movement rate and telescope's aim point on screen
		if ((mmInStream != null) && (mmTimer != null)){
			// stop periodic update task so it does not interfere by sending Z commands
			if(mmTimer != null){
				mmTimer.cancel();
				mmTimer.purge();
				mmTimer = null;
				// wait for a short period to let stray bytes get captured
				try{
					sleep(TELESCOPE_INIT_TIMEOUT);
				}catch(InterruptedException e){}
			}
			TelescopeListen.clear = true;	// clear buffer upon start
			TelescopeListen.interrupt = false;	// don't want the listening thread to interrupt us
			// Before trying to re-establish high-level connection with telescope
			// we will purge the buffers
			// send interrogate command just to try make telescope send something back to let us clear buffer
			write("?".getBytes());
			// wait for a short period to let stray bytes get captured
			try{
				sleep(TELESCOPE_REPLY_TIMEOUT);
			}catch(InterruptedException e){}
			
			// Cancel telescope's tracking (sideral, solar, moon, whatever...)
			TelescopeListen.clear = true;
			TelescopeListen.interrupt = true;
			byte[] command = new byte[2];
			command[0] = (byte)"T".charAt(0);
			command[1] = 0;
			write(command);
			try{
				sleep(TELESCOPE_REPLY_TIMEOUT);
			}catch(InterruptedException e){	}
			if(buffer[0] != '#'){
				if(TELESCOPE_DEBUG != 0){
					Toast msg = Toast
							.makeText(
									ShowSatellites.instance,
									"No response to sideral-tracking cancel",
									Toast.LENGTH_LONG);
					msg.show();
				}
				// reconnection not successfull
				return false;
			}
			// restart control thread
			errCycleCount = 0;
			timeStamp = System.nanoTime();
			// reschedule timer in case parameters changed
			mmTimer = new Timer();
			TimerTask t = new TelescopeUpdate();
			mmTimer.scheduleAtFixedRate(t, cycleTime, cycleTime);
			return true;
		}
		return false;
	}
	
	// End telescope tracking without closing communications 
	public static void endTracking(){
		if(mmInStream != null){
			// stop periodic update task so it does not interfere by sending Z commands
			if(mmTimer != null){
				mmTimer.cancel();
				mmTimer.purge();
				mmTimer = null;
				// wait for a short period to let stray bytes get captured
				try{
					sleep(TELESCOPE_INIT_TIMEOUT);
				}catch(InterruptedException e){}
			}
			// Try to stop telescope Azimuth slew
			TelescopeListen.clear = true;	// clear buffer 
			TelescopeListen.interrupt = true;	// now we do want to be interrupted at reception of the end-character
			byte[] command = new byte[8];
			command[0] = (byte)"P".charAt(0);
			command[1] = 3;
			command[2] = 16;
			command[3] = 6;
			command[4] = 0;
			command[5] = 0;
			command[6] = 0;
			command[7] = 0;
			// send Az slew command
			write(command);
			// Wait a little bit before closing the stream to let telescope receive the commands
			// this wait will be interrupted if listener receives command-end character ('#') 
			try{
				sleep(TELESCOPE_REPLY_TIMEOUT);
			}catch(InterruptedException e){	}
			if((TELESCOPE_DEBUG != 0) && (buffer[0] != '#')){
				Toast msg = Toast
						.makeText(
								ShowSatellites.instance,
								"No response to Az slew cancel",
								Toast.LENGTH_LONG);
				msg.show();
			}
			// Try to stop telescope Azimuth slew
			TelescopeListen.clear = true;	// clear buffer 
			TelescopeListen.interrupt = true;	// now we do want to be interrupted at reception of the end-character
			command[0] = (byte)"P".charAt(0);
			command[1] = 3;
			command[2] = 17;
			command[3] = 6;
			command[4] = 0;
			command[5] = 0;
			command[6] = 0;
			command[7] = 0;
			// send El slew command
			write(command);
			// Wait a little bit before closing the stream to let telescope receive the commands
			// this wait will be interrupted if listener receives command-end character ('#') 
			try{
				sleep(TELESCOPE_REPLY_TIMEOUT);
			}catch(InterruptedException e){	}
			if((TELESCOPE_DEBUG != 0) && (buffer[0] != '#')){
				Toast msg = Toast
						.makeText(
								ShowSatellites.instance,
								"No response to El slew cancel",
								Toast.LENGTH_LONG);
				msg.show();
			}
			// reschedule timer at 1/4 rate while not tracking just to show aim coords on screen
			mmTimer = new Timer();
			TimerTask t = new TelescopeUpdate();
			mmTimer.scheduleAtFixedRate(t, cycleTime*4, cycleTime*4);
		}
	}


	// This method searches the provided set of devices for a Celestron Telescope
	// Returns the connected socket
	public BluetoothSocket searchBtDevices(Set<BluetoothDevice> pairedDevices){
		
		BluetoothSocket tmpSocket = null;

		// poll all devices
		for(BluetoothDevice device : pairedDevices){
			// Try open client socket for each device
			try{
				tmpSocket = device.createInsecureRfcommSocketToServiceRecord(mmUUID);
			} catch (IOException e) {
				// could not open this device, try next
				continue;
			}
			// socket seems to have opened, get it
			mmSocket = tmpSocket;
			tmpSocket = null;		// erase tmpSocket, will reload it if get succesfull connection
			// try connect socket
			try{
				mmSocket.connect();
			}catch (IOException connectException){
				// no luck, attempt to close
				try{
					mmSocket.close();
				}catch (IOException closeException){}
				// go ahead to next device on paired list
				continue;
			}
			// if we reached here then socket should be open and connected
			// will try to establish comms with the telescope
			initStreams(mmSocket);
			// instantiate listening thread
			TelescopeListen.clear = true;	// clear buffer upon start
			TelescopeListen.interrupt = false;	// don't want the listening thread to interrupt us
			Thread t = new Thread(new TelescopeListen());
			t.start();
			// Before trying to establish high-level connection with telescope
			// we will purge the buffers
			// wait for a short period to let stray bytes get captured
			try{
				sleep(TELESCOPE_INIT_TIMEOUT);
			}catch(InterruptedException e){}
			
			TelescopeListen.clear = true;	// clear buffer 
			TelescopeListen.interrupt = true;	// now we do want to be interrupted at reception of the end-character
			// Send echo command to check comms
			write("K*".getBytes());		// ask the telescope to echo a '*' back
			// this wait will be interrupted if listener thread receives command-end character ('#') 
			try{
				sleep(TELESCOPE_REPLY_TIMEOUT);
			}catch(InterruptedException e){	}
			// Read bytes received, if got a '*' take it as valid reply from telescope 
			if(buffer[0] == '*'){
				// Found Telescope!!
				// will check if it is aligned...
				TelescopeListen.clear = true;
				TelescopeListen.interrupt = true;
				write("J".getBytes());
				try{
					sleep(TELESCOPE_REPLY_TIMEOUT);
				}catch(InterruptedException e){	}
				// Read bytes received, if got a 1 (not ASCII) it is aligned... 
				if(buffer[0] == 1){
					// Telescope is aligned... all OK!
					// tmpSocket will carry the socket from which we got the answer
					tmpSocket = mmSocket;
					break;		// exit the telescope search loop
				}
				else{
					Toast msg = Toast
							.makeText(
									ShowSatellites.instance,
									"Telescope not aligned. Cannot track.",
									Toast.LENGTH_LONG);
					msg.show();
					// in this case we do not try other devices, we assume there is only one telescope
					// and user will align it and try again later
					// we are leaving the socket and the stream open
					break;
				}
			}
			// Remote device did not reply after open, maybe it was not a Celestron telescope!... try next device
			if(mmInStream != null){
				try{
					mmInStream.close();
					mmInStream = null;
				}catch(IOException e){}
			}
			if(mmSocket != null){
				try{
					mmSocket.close();
					mmSocket = null;
				}catch(IOException e){}
			}
		}
		// tmpSocket is null if no valid reply from telescope was received from any of the paired devices
		// otherwise tmpSocket points to the connected socket where the telescope can be contacted...
		return tmpSocket;
	}
}